﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Threading;

using SharedGenomics.Core;
using SharedGenomics.Core.Collections;
using SharedGenomics.DataTransfer;
using SharedGenomics.Annotations;
using SharedGenomics.Workbench.Services;

namespace SharedGenomics.Workbench.Annotations.Requests
{
    public class NcbiAnnotationRequest : IAnnotationRequestStage
    {
        private readonly IAnnotationService _annotationService;
        private const int QUERY_COUNT = 50;
        private BoundedTimedBuffer<string> _markerNameBuffer;
        private SignalledQueue<AnnotationRequestState> _outputQueue;
        private IDictionary<string, AnnotationRequestState> _snpRequestMap;
        private object _mapLock = new object();

        public NcbiAnnotationRequest(IAnnotationService annotationService)
        {
            this._markerNameBuffer = new BoundedTimedBuffer<string>(QUERY_COUNT, new TimeSpan(0, 0, 3), this.Query);
            this._annotationService = annotationService;
            this._outputQueue = new SignalledQueue<AnnotationRequestState>(rs => rs.TraversalStrategy.MoveNext(rs.Context), 3);
            this._snpRequestMap = new Dictionary<string, AnnotationRequestState>(StringComparer.InvariantCultureIgnoreCase);
        }

        public void Process(AnnotationRequestContext context, IAnnotationRequestPipelineTraversalStrategy traversalStrategy)
        {
            string markerName = context.AnnotationTarget as string;
            if (markerName != null)
            {
                lock (this._mapLock)
                {
                    this._snpRequestMap.Add(markerName, new AnnotationRequestState(context, traversalStrategy));
                }

                this._markerNameBuffer.Add(markerName);
            }
            else
            {
                //we cannot deal with the request so move to the next stage
                traversalStrategy.MoveNext(context);
            }
        }

        //NOTE: this should be executed on a background thread i.e. one other than one used for the Process method
        private void Query()
        {
            HashSet<string> markerNames = new HashSet<string>(this._markerNameBuffer.UnBuffer(QUERY_COUNT), StringComparer.InvariantCultureIgnoreCase);

            if (markerNames.Count > 0)
            {
                //execute query
                try
                {
                    var infosMaybe = this._annotationService.GetNcbiInfos(markerNames);
                    if (infosMaybe.HasValue)
                    {
                        markerNames.ExceptWith(infosMaybe.Value.Select(ncbiInfo => ncbiInfo.MarkerName));

                        //any marker name remaining in the markerNames set was not returned by the 
                        this.FailMarkerRequests(markerNames);
                        this.SucceedMarkerRequests(infosMaybe.Value);
                    }
                    else
                    {
                        //nothing returned so fail all requests
                        this.FailMarkerRequests(markerNames);
                    }
                }
                catch (Exception)
                {
                    //request failed so fail all associated annotation requests
                    this.FailMarkerRequests(markerNames);
                }
            }
        }

        private void FailMarkerRequests(IEnumerable<string> markerNames)
        {
            Debug.Assert(markerNames != null);

            AnnotationRequestState currentRequest;
            bool foundRequest;
            foreach (string marker in markerNames)
            {
                //remove request context from list of unprocessed items and add it into the queue to be passed to the next stage
                lock (this._mapLock)
                {
                    if (foundRequest = this._snpRequestMap.TryGetValue(marker, out currentRequest))
                    {
                        this._snpRequestMap.Remove(marker);
                    }
                }

                if (foundRequest)
                {
                    currentRequest.Context.FailRequest(AnnotationKey.NcbiSummary);
                    this._outputQueue.Enqueue(currentRequest);
                }
            }
        }

        private void SucceedMarkerRequests(IEnumerable<NcbiInfo> ncbiInfos)
        {
            Debug.Assert(ncbiInfos != null);

            AnnotationRequestState currentRequest;
            bool foundRequest;

            foreach (NcbiInfo info in ncbiInfos)
            {
                lock(this._mapLock)
                {
                    if (foundRequest = this._snpRequestMap.TryGetValue(info.MarkerName, out currentRequest))
                    {
                        //remove corresponding request context from list of unprocessed items
                        this._snpRequestMap.Remove(info.MarkerName);
                    }
                }

                if (foundRequest)
                {
                    //update context with annotation and associated values
                    var context = currentRequest.Context;
                    var annotation = new NcbiAnnotation(null, info.MarkerName, info.EntrezGeneId, new[] { info.GeneName }, info.Chromosome, info.BasePosition);
                    context.AddAnnotation(annotation, new AnnotationValue(AnnotationKey.SnpName, context.AnnotationTarget));

                    //TODO: add EntrezGeneId annotation value if it exists
                    if (info.EntrezGeneId.HasValue)
                    {
                        context.AddValue(new AnnotationValue(AnnotationKey.EntrezGeneId, info.EntrezGeneId), new AnnotationValue(AnnotationKey.SnpName, context.AnnotationTarget));
                    }

                    //add to queue of items to be passed to the next stage
                    this._outputQueue.Enqueue(currentRequest);
                }
            }
        }
    }
}